#ifndef __ZPP_UNIT_HPP__
#define __ZPP_UNIT_HPP__
#include <chrono>
#include <ratio>

namespace zpp {

template <typename T, typename TRatio, auto TTag = [] {}> class unit {
public:
  using Ratio = TRatio;
  T value() const { return m_value; }
  auto &operator=(unit &other) {
    m_value = other.m_value;
    return *this;
  }

private:
  T m_value;
};

#define ZPP_UNIT_BASE_DEFINE(unit_name, value_type)                            \
  template <typename TRatio = std::ratio<1, 1>>                                \
  class unit_name : public unit<value_type, TRatio> {}

#define ZPP_KILO_UNIT_DEFINE(unit_base)                                        \
  using k##unit_base = unit_base<std::kilo>;
#define ZPP_MEGA_UNIT_DEFINE(unit_base)                                        \
  using M##unit_base = unit_base<std::mega>;
#define ZPP_MILlI_UNIT_DEFINE(unit_base)                                       \
  using m##unit_base = unit_base<std::milli>;

ZPP_UNIT_BASE_DEFINE(hz, uint32_t);
ZPP_KILO_UNIT_DEFINE(hz);
ZPP_MEGA_UNIT_DEFINE(hz);

namespace units {

template <typename T>
  requires std::is_enum_v<T>
inline auto operator+(T a, T b) -> T {
  return static_cast<T>(static_cast<std::underlying_type_t<T>>(a) +
                        static_cast<std::underlying_type_t<T>>(b));
}

template <typename T>
  requires std::is_enum_v<T>
inline auto operator-(T a, T b) -> T {
  return static_cast<T>(static_cast<std::underlying_type_t<T>>(a) -
                        static_cast<std::underlying_type_t<T>>(b));
}

} // namespace units
} // namespace zpp

#endif // __ZPP_UNIT_HPP__